package edu.bbk.muc.lifetracker;

import java.util.Date;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.util.Log;
import edu.bbk.muc.lifetracker.common.Event;

/**
 * Provides interface to data storage layer (SQLite).
 * 
 * @author irenatyler and romankirillov
 */
public class DbAdapter {
  /**
   * Helper class for talking to SQLite
   */
  private static class DatabaseHelper extends SQLiteOpenHelper {
    DatabaseHelper(Context context) {
      super(context, DB_NAME, null, DB_VERSION);
      Log.i(TAG, "Created DatabaseHelper");
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
      Log.i(TAG, "creating database");
      db.execSQL(SQL_DB_CREATE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
      Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
                + newVersion + ", which will destroy all old data");
      dropDatabase(db);
      onCreate(db);
    }

    private void dropDatabase(SQLiteDatabase db) {
      Log.i(TAG, "Dropping database");
      db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
    }
  }

  private static final String DB_NAME = "data";
  private static final String DB_TABLE = "events";
  private static final int DB_VERSION = 2;
  public static final String KEY_ROW_ID = "_id";
  public static final String KEY_TIME = "time";
  public static final String KEY_TITLE = "title";

  private SQLiteStatement countStatement;

  private static final String SQL_DB_CREATE = "CREATE TABLE IF NOT EXISTS " + DB_TABLE +
      "(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
      "title TEXT NOT NULL," +
      "time INTEGER NOT NULL);";

  private static final String TAG = "DbAdapter";

  private final Context context;
  private DatabaseHelper databaseHelper;
  private SQLiteDatabase sqliteDatabase;

  private final EventReceiver eventReceiver = new EventReceiver() {
    private EventReceiver next;

    @Override
    public void addChainedEventReceiver(EventReceiver next) {
      this.next = next;
    }

    @Override
    public void onEventReady(Event event) {
      String data = event.toJSON();
      long time = event.getTimestamp();

      createEvent(data, time);

      if (next != null) {
        next.onEventReady(event);
      }
    }
  };

  /**
   * Public constructor. Should be either called within Activity (and passed
   * <code>this</code> for context) or, for unit tests, Activity test must be
   * created, in which case getActivity() should be passed for context.
   * 
   * @param context
   */
  public DbAdapter(Context context) {
    this.context = context;
  }

  public void clearAllEvents() {
    sqliteDatabase.execSQL("DELETE FROM " + DB_TABLE);
  }

  /**
   * This must be called explicitly, at least for tests
   */
  public void close() {
    databaseHelper.close();
  }

  public long createEvent(String text) {
    return createEvent(text, new Date().getTime() / 1000);
  }

  /**
   * Creates new Event, for now only with text and time. We may want to change
   * this method later to take it some POJO instead of multiple arguments.
   * 
   * @param text
   *          Text of the event
   * @param time
   *          Time of the event. If not specified (see
   *          {@link #createEvent(String)}), current time will be used.
   * @return rowId of the created entry.
   */
  public long createEvent(String text, long time) {
    ContentValues initialValues = new ContentValues();
    initialValues.put(KEY_TITLE, text);
    initialValues.put(KEY_TIME, time);

    return sqliteDatabase.insert(DB_TABLE, null, initialValues);
  }

  /**
   * Deletes event from the table by rowId
   * 
   * @param rowId
   *          row to be deleted
   * @return <code>true</code> if more than 0 records were deleted (should be
   *         one normally), <code>false</code> otherwise.
   */
  public boolean deleteEvent(long rowId) {
    return sqliteDatabase.delete(DB_TABLE, KEY_ROW_ID + "=" + rowId, null) > 0;
  }

  /**
   * This method is used only for unit tests so we can start with 'clear'
   * stateless database. Supposed to be called in tearDown.
   */
  public void dropDatabase() {
    databaseHelper.dropDatabase(sqliteDatabase);
  }

  /**
   * Fetches all events from the table.
   */
  public Cursor fetchAllEvents() {
    return sqliteDatabase.query(
        DB_TABLE,
        new String[] { KEY_ROW_ID, KEY_TITLE, KEY_TIME },
        null, null, null, null, null);
  }

  /**
   * Fetches event by rowId
   * 
   * @param rowId
   * 
   * @return open cursor if record was found or <code>null</code> otherwise.
   */
  public Cursor fetchEvent(long rowId) {
    String[] columns = new String[] { KEY_ROW_ID, KEY_TITLE, KEY_TIME };
    Cursor cursor = sqliteDatabase.query(true,
                                        DB_TABLE,
                                        columns,
                                        KEY_ROW_ID + "=" + rowId,
                                        null, null, null, null, null);

    if (cursor != null) {
      cursor.moveToFirst();
    }

    return cursor;
  }

  public EventReceiver getEventReceiver() {
    return eventReceiver;
  }

  public long getNumEvents() {
    return countStatement.simpleQueryForLong();
  }

  public DbAdapter open() {
    databaseHelper = new DatabaseHelper(context);
    sqliteDatabase = databaseHelper.getWritableDatabase();

    databaseHelper.onCreate(sqliteDatabase);
    countStatement = sqliteDatabase.compileStatement("SELECT COUNT(*) FROM " + DB_TABLE);

    return this;
  }
}
